/*
 * LiquidBounce Hacked Client
 * A free open source mixin-based injection hacked client for Minecraft using Minecraft Forge.
 * https://github.com/CCBlueX/LiquidBounce/
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit;

import net.ccbluex.liquidbounce.api.enums.MaterialType;
import net.ccbluex.liquidbounce.api.minecraft.block.state.IIBlockState;
import net.ccbluex.liquidbounce.api.minecraft.client.entity.IEntityPlayerSP;
import net.ccbluex.liquidbounce.api.minecraft.network.IPacket;
import net.ccbluex.liquidbounce.api.minecraft.network.play.client.ICPacketEntityAction;
import net.ccbluex.liquidbounce.api.minecraft.network.play.client.ICPacketPlayer;
import net.ccbluex.liquidbounce.api.minecraft.renderer.entity.IRenderManager;
import net.ccbluex.liquidbounce.api.minecraft.util.IMovingObjectPosition;
import net.ccbluex.liquidbounce.api.minecraft.util.WBlockPos;
import net.ccbluex.liquidbounce.api.minecraft.util.WVec3;
import net.ccbluex.liquidbounce.event.*;
import net.ccbluex.liquidbounce.features.module.Module;
import net.ccbluex.liquidbounce.features.module.ModuleCategory;
import net.ccbluex.liquidbounce.features.module.ModuleInfo;
import net.ccbluex.liquidbounce.utils.ClientUtils;
import net.ccbluex.liquidbounce.utils.MovementUtils;
import net.ccbluex.liquidbounce.utils.PathUtils;
import net.ccbluex.liquidbounce.utils.block.BlockUtils;
import net.ccbluex.liquidbounce.utils.render.RenderUtils;
import net.ccbluex.liquidbounce.utils.timer.TickTimer;
import net.ccbluex.liquidbounce.value.BoolValue;
import net.ccbluex.liquidbounce.value.ListValue;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.lwjgl.opengl.GL11.*;

@ModuleInfo(name = "Teleport", description = "Allows you to teleport around.", category = ModuleCategory.EXPLOIT)
public class Teleport extends Module {

    private final BoolValue ignoreNoCollision = new BoolValue("IgnoreNoCollision", true);
    private final ListValue modeValue = new ListValue("Mode", new String[] {"Blink", "Flag", "Rewinside", "OldRewinside", "Spoof", "Minesucht", "AAC3.5.0"}, "Blink");
    private final ListValue buttonValue = new ListValue("Button", new String[] {"Left", "Right", "Middle"}, "Middle");
    private final TickTimer flyTimer = new TickTimer();
    private boolean hadGround;
    private double fixedY;
    private final List<IPacket> packets = new ArrayList<>();
    private boolean disableLogger = false;
    private boolean zitter = false;
    private boolean doTeleport = false;
    private boolean freeze = false;
    private final TickTimer freezeTimer = new TickTimer();

    private int delay;
    private WBlockPos endPos;
    private IMovingObjectPosition objectPosition;

    @Override
    public void onEnable() {
        if (modeValue.get().equalsIgnoreCase("AAC3.5.0")) {
            ClientUtils.displayChatMessage("§c>>> §a§lTeleport §fAAC 3.5.0 §c<<<");
            ClientUtils.displayChatMessage("§cHow to teleport: §aPress " + buttonValue.get() + " mouse button.");
            ClientUtils.displayChatMessage("§cHow to cancel teleport: §aDisable teleport module.");
        }
    }

    @Override
    public void onDisable() {
        fixedY = 0D;
        delay = 0;
        mc.getTimer().setTimerSpeed(1F);
        endPos = null;
        hadGround = false;
        freeze = false;
        disableLogger = false;
        flyTimer.reset();

        packets.clear();

        super.onDisable();
    }

    @EventTarget
    public void onUpdate(final UpdateEvent event) {
        final int buttonIndex = Arrays.asList(buttonValue.getValues()).indexOf(buttonValue.get());

        IEntityPlayerSP thePlayer = mc.getThePlayer();

        if (thePlayer == null)
            return;

        if (modeValue.get().equals("AAC3.5.0")) {
            freezeTimer.update();

            if (freeze && freezeTimer.hasTimePassed(40)) {
                freezeTimer.reset();
                freeze = false;
                setState(false);
            }

            if (!flyTimer.hasTimePassed(60)) {
                flyTimer.update();

                if (thePlayer.getOnGround()) {
                    thePlayer.jump();
                } else {
                    MovementUtils.forward(zitter ? -0.21D : 0.21D);
                    zitter = !zitter;
                }

                hadGround = false;
                return;
            }

            if (thePlayer.getOnGround())
                hadGround = true;

            if (!hadGround)
                return;

            if (thePlayer.getOnGround())
                thePlayer.setPositionAndUpdate(thePlayer.getPosX(), thePlayer.getPosY() + 0.2D, thePlayer.getPosZ());

            final float vanillaSpeed = 2F;

            thePlayer.getCapabilities().setFlying(false);

            thePlayer.setMotionX(0.0);
            thePlayer.setMotionY(0.0);
            thePlayer.setMotionZ(0.0);

            if (mc.getGameSettings().getKeyBindJump().isKeyDown())
                thePlayer.setMotionY(thePlayer.getMotionY() + vanillaSpeed);
            if (mc.getGameSettings().getKeyBindSneak().isKeyDown())
                thePlayer.setMotionY(thePlayer.getMotionY() - vanillaSpeed);
            MovementUtils.strafe(vanillaSpeed);

            if (Mouse.isButtonDown(buttonIndex) && !doTeleport) {
                thePlayer.setPositionAndUpdate(thePlayer.getPosX(), thePlayer.getPosY() - 11, thePlayer.getPosZ());

                disableLogger = true;
                packets.forEach(packet -> mc.getNetHandler().addToSendQueue(packet));

                freezeTimer.reset();
                freeze = true;
            }

            doTeleport = Mouse.isButtonDown(buttonIndex);
            return;
        }

        if (mc.getCurrentScreen() == null && Mouse.isButtonDown(buttonIndex) && delay <= 0) {
            endPos = objectPosition.getBlockPos();


            IIBlockState state = BlockUtils.getState(endPos);

            if (state.getBlock().getMaterial(state).equals(classProvider.getMaterialEnum(MaterialType.AIR))) {
                endPos = null;
                return;
            }

            ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3Position was set to §8" + endPos.getX() + "§3, §8" + ((BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()) == null ? endPos.getY() + 1 : BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()).getMaxY()) + fixedY) + "§3, §8" + endPos.getZ());
            delay = 6;
        }

        if(delay > 0)
            --delay;

        if(endPos != null) {
            final double endX = (double) endPos.getX() + 0.5D;
            final double endY = (BlockUtils.getBlock(objectPosition.getBlockPos())
                    .getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()) == null ? endPos.getY() + 1 : BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()).getMaxY()) + fixedY;
            final double endZ = (double) endPos.getZ() + 0.5D;

            switch(modeValue.get().toLowerCase()) {
                case "blink":
                    if (thePlayer.isSneaking()) {
                        // Sneak
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketEntityAction(thePlayer, ICPacketEntityAction.WAction.STOP_SNEAKING));

                        // Teleport
                        PathUtils.findBlinkPath(endX, endY, endZ).forEach(vector3d -> {
                            mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(vector3d.x, vector3d.y, vector3d.z, true));
                            thePlayer.setPosition(endX, endY, endZ);
                        });

                        // Sneak
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketEntityAction(thePlayer, ICPacketEntityAction.WAction.START_SNEAKING));

                        // Notify
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3You were teleported to §8" + endX + "§3, §8" + endY + "§3, §8" + endZ);
                    }
                    break;
                case "flag":
                    if (thePlayer.isSneaking()) {
                        // Sneak
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketEntityAction(thePlayer, ICPacketEntityAction.WAction.STOP_SNEAKING));

                        // Teleport
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY() + 5D, thePlayer.getPosZ(), true));
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX() + 0.5D, thePlayer.getPosY(), thePlayer.getPosZ() + 0.5D, true));

                        MovementUtils.forward(0.04D);

                        // Sneak
                        mc.getNetHandler().addToSendQueue(classProvider.createCPacketEntityAction(thePlayer, ICPacketEntityAction.WAction.START_SNEAKING));
                        // Notify
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3You were teleported to §8" + endX + "§3, §8" + endY + "§3, §8" + endZ);
                    }
                    break;
                case "rewinside":
                    thePlayer.setMotionY(0.1);
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY() + 0.6D, thePlayer.getPosZ(), true));

                    if ((int) thePlayer.getPosX() == (int) endX && (int) thePlayer.getPosY() == (int) endY && (int) thePlayer.getPosZ() == (int) endZ) {
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3You were teleported to §8" + endX + "§3, §8" + endY + "§3, §8" + endZ);
                        endPos = null;
                    } else
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3Teleport try...");
                    break;
                case "oldrewinside":
                    thePlayer.setMotionY(0.1);

                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(thePlayer.getPosX(), thePlayer.getPosY(), thePlayer.getPosZ(), true));

                    if ((int) thePlayer.getPosX() == (int) endX && (int) thePlayer.getPosY() == (int) endY && (int) thePlayer.getPosZ() == (int) endZ) {
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3You were teleported to §8" + endX + "§3, §8" + endY + "§3, §8" + endZ);
                        endPos = null;
                    } else
                        ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3Teleport try...");

                    MovementUtils.forward(0.04D);
                    break;
                case "minesucht":
                    if (!thePlayer.isSneaking())
                        break;

                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(endX, endY, endZ, true));
                    ClientUtils.displayChatMessage("§7[§8§lTeleport§7] §3You were teleported to §8" + endX + "§3, §8" + endY + "§3, §8" + endZ);
                    break;
            }
        }
    }

    @EventTarget
    public void onRender3D(final Render3DEvent event) {
        if (modeValue.get().equals("AAC3.5.0"))
            return;

        IEntityPlayerSP thePlayer = mc.getThePlayer();

        if (thePlayer == null) {
            return;
        }


        WVec3 entityLookVec = thePlayer.getLookVec();

        final WVec3 lookVec = new WVec3(entityLookVec.getXCoord() * 300, entityLookVec.getYCoord() * 300, entityLookVec.getZCoord() * 300);
        final WVec3 posVec = new WVec3(thePlayer.getPosX(), thePlayer.getPosY() + 1.62, thePlayer.getPosZ());

        objectPosition = mc.getTheWorld().rayTraceBlocks(posVec, posVec.add(lookVec), false, ignoreNoCollision.get(), false);

        if (objectPosition == null || objectPosition.getBlockPos() == null)
            return;

        final WBlockPos belowBlockPos = new WBlockPos(objectPosition.getBlockPos().getX(), objectPosition.getBlockPos().getY() - 1, objectPosition.getBlockPos().getZ());

        fixedY = classProvider.isBlockFence(BlockUtils.getBlock(objectPosition.getBlockPos())) ? (mc.getTheWorld().getCollidingBoundingBoxes(thePlayer, thePlayer.getEntityBoundingBox().offset(objectPosition.getBlockPos().getX() + 0.5D - thePlayer.getPosX(), objectPosition.getBlockPos().getY() + 1.5D - thePlayer.getPosY(), objectPosition.getBlockPos().getZ() + 0.5D - thePlayer.getPosZ())).isEmpty() ? 0.5D : 0D) : classProvider.isBlockFence(BlockUtils.getBlock(belowBlockPos)) ? (!mc.getTheWorld().getCollidingBoundingBoxes(thePlayer, thePlayer.getEntityBoundingBox().offset(objectPosition.getBlockPos().getX() + 0.5D - thePlayer.getPosX(), objectPosition.getBlockPos().getY() + 0.5D - thePlayer.getPosY(), objectPosition.getBlockPos().getZ() + 0.5D - thePlayer.getPosZ())).isEmpty() || BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()) == null ? 0D : 0.5D - 1) : classProvider.isBlockSnow(BlockUtils.getBlock(objectPosition.getBlockPos())) ? 1 - 0.125D : 0D;

        final int x = objectPosition.getBlockPos().getX();
        final double y = (BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()) == null ? objectPosition.getBlockPos().getY() + 1 : BlockUtils.getBlock(objectPosition.getBlockPos()).getCollisionBoundingBox(mc.getTheWorld(), objectPosition.getBlockPos(), BlockUtils.getBlock(objectPosition.getBlockPos()).getDefaultState()).getMaxY()) - 1D + fixedY;
        final int z = objectPosition.getBlockPos().getZ();

        if (!classProvider.isBlockAir(BlockUtils.getBlock(objectPosition.getBlockPos()))) {
            final IRenderManager renderManager = mc.getRenderManager();

            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glLineWidth(2F);
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_DEPTH_TEST);
            glDepthMask(false);
            RenderUtils.glColor(modeValue.get().equalsIgnoreCase("minesucht") && thePlayer.getPosition().getY() != y + 1 ? new Color(255, 0, 0, 90) : !mc.getTheWorld().getCollidingBoundingBoxes(thePlayer, thePlayer.getEntityBoundingBox().offset(x + 0.5D - thePlayer.getPosX(), y + 1D - thePlayer.getPosY(), z + 0.5D - thePlayer.getPosZ())).isEmpty() ? new Color(255, 0, 0, 90) : new Color(0, 255, 0, 90));
            RenderUtils.drawFilledBox(classProvider.createAxisAlignedBB(x - renderManager.getRenderPosX(), (y + 1) - renderManager.getRenderPosY(), z - renderManager.getRenderPosZ(), x - renderManager.getRenderPosX() + 1.0D, y + 1.2D - renderManager.getRenderPosY(), z - renderManager.getRenderPosZ() + 1.0D));
            glEnable(GL_TEXTURE_2D);
            glEnable(GL_DEPTH_TEST);
            glDepthMask(true);
            glDisable(GL_BLEND);

            RenderUtils.renderNameTag(Math.round(thePlayer.getDistance(x + 0.5D, y + 1D, z + 0.5D)) + "m", x + 0.5, y + 1.7, z + 0.5);
            GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    @EventTarget
    public void onMove(final MoveEvent event) {
        if (modeValue.get().equalsIgnoreCase("aac3.5.0") && freeze) {
            event.zeroXZ();
        }
    }

    @EventTarget
    public void onPacket(final PacketEvent event) {
        final IPacket packet = event.getPacket();

        if (disableLogger)
            return;

        if (classProvider.isCPacketPlayer(packet)) {
            final ICPacketPlayer packetPlayer = packet.asCPacketPlayer();

            switch (modeValue.get().toLowerCase()) {
                case "spoof":
                    if (endPos == null)
                        break;

                    packetPlayer.setX(endPos.getX() + 0.5D);
                    packetPlayer.setY(endPos.getY() + 1);
                    packetPlayer.setZ(endPos.getZ() + 0.5D);

                    mc.getThePlayer().setPosition(endPos.getX() + 0.5D, endPos.getY() + 1, endPos.getZ() + 0.5D);
                    break;
                case "aac3.5.0":
                    if (!flyTimer.hasTimePassed(60))
                        return;

                    event.cancelEvent();

                    if (!(classProvider.isCPacketPlayerPosition(packet)) && !(classProvider.isCPacketPlayerPosLook(packet)))
                        return;

                    packets.add(packet);
                    break;
            }
        }
    }

    @Override
    public String getTag() {
        return modeValue.get();
    }
}
